/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.catalina.core;

import org.apache.catalina.Executor;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.tomcat.util.threads.ResizableExecutor;
import org.apache.tomcat.util.threads.TaskQueue;
import org.apache.tomcat.util.threads.TaskThreadFactory;
import org.apache.tomcat.util.threads.ThreadPoolExecutor;

import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

public class StandardThreadExecutor extends LifecycleMBeanBase
		implements Executor, ResizableExecutor {

	// ---------------------------------------------- Properties
	/**
	 * Default thread priority
	 */
	protected int threadPriority = Thread.NORM_PRIORITY;

	/**
	 * Run threads in daemon or non-daemon state
	 */
	protected boolean daemon = true;

	/**
	 * Default name prefix for the thread name
	 */
	protected String namePrefix = "tomcat-exec-";

	/**
	 * max number of threads
	 */
	protected int maxThreads = 200;

	/**
	 * min number of threads
	 */
	protected int minSpareThreads = 25;

	/**
	 * idle time in milliseconds
	 */
	protected int maxIdleTime = 60000;

	/**
	 * The executor we use for this component
	 */
	protected ThreadPoolExecutor executor = null;

	/**
	 * the name of this thread pool
	 */
	protected String name;

	/**
	 * prestart threads?
	 */
	protected boolean prestartminSpareThreads = false;

	/**
	 * The maximum number of elements that can queue up before we reject them
	 */
	protected int maxQueueSize = Integer.MAX_VALUE;

	/**
	 * After a context is stopped, threads in the pool are renewed. To avoid
	 * renewing all threads at the same time, this delay is observed between 2
	 * threads being renewed.
	 */
	protected long threadRenewalDelay =
			org.apache.tomcat.util.threads.Constants.DEFAULT_THREAD_RENEWAL_DELAY;

	private TaskQueue taskqueue = null;

	// ---------------------------------------------- Constructors
	public StandardThreadExecutor() {
		//empty constructor for the digester
	}


	// ---------------------------------------------- Public Methods

	@Override
	protected void initInternal() throws LifecycleException {
		super.initInternal();
	}

	/**
	 * Start the component and implement the requirements
	 * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
	 *
	 * @throws LifecycleException if this component detects a fatal error
	 *                            that prevents this component from being used
	 */
	@Override
	protected void startInternal() throws LifecycleException {

		taskqueue = new TaskQueue(maxQueueSize);
		TaskThreadFactory tf = new TaskThreadFactory(namePrefix, daemon, getThreadPriority());
		executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), maxIdleTime, TimeUnit.MILLISECONDS, taskqueue, tf);
		executor.setThreadRenewalDelay(threadRenewalDelay);
		if (prestartminSpareThreads) {
			executor.prestartAllCoreThreads();
		}
		taskqueue.setParent(executor);

		setState(LifecycleState.STARTING);
	}

	/**
	 * Stop the component and implement the requirements
	 * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
	 *
	 * @throws LifecycleException if this component detects a fatal error
	 *                            that needs to be reported
	 */
	@Override
	protected void stopInternal() throws LifecycleException {

		setState(LifecycleState.STOPPING);
		if (executor != null) executor.shutdownNow();
		executor = null;
		taskqueue = null;
	}

	@Override
	protected void destroyInternal() throws LifecycleException {
		super.destroyInternal();
	}

	@Override
	public void execute(Runnable command, long timeout, TimeUnit unit) {
		if (executor != null) {
			executor.execute(command, timeout, unit);
		} else {
			throw new IllegalStateException("StandardThreadExecutor not started.");
		}
	}

	@Override
	public void execute(Runnable command) {
		if (executor != null) {
			try {
				executor.execute(command);
			} catch (RejectedExecutionException rx) {
				//there could have been contention around the queue
				if (!((TaskQueue) executor.getQueue()).force(command))
					throw new RejectedExecutionException("Work queue full.");
			}
		} else throw new IllegalStateException("StandardThreadPool not started.");
	}

	public void contextStopping() {
		if (executor != null) {
			executor.contextStopping();
		}
	}

	public int getThreadPriority() {
		return threadPriority;
	}

	public void setThreadPriority(int threadPriority) {
		this.threadPriority = threadPriority;
	}

	public boolean isDaemon() {

		return daemon;
	}

	public void setDaemon(boolean daemon) {
		this.daemon = daemon;
	}

	public String getNamePrefix() {
		return namePrefix;
	}

	public void setNamePrefix(String namePrefix) {
		this.namePrefix = namePrefix;
	}

	public int getMaxIdleTime() {
		return maxIdleTime;
	}

	public void setMaxIdleTime(int maxIdleTime) {
		this.maxIdleTime = maxIdleTime;
		if (executor != null) {
			executor.setKeepAliveTime(maxIdleTime, TimeUnit.MILLISECONDS);
		}
	}

	@Override
	public int getMaxThreads() {
		return maxThreads;
	}

	public void setMaxThreads(int maxThreads) {
		this.maxThreads = maxThreads;
		if (executor != null) {
			executor.setMaximumPoolSize(maxThreads);
		}
	}

	public int getMinSpareThreads() {
		return minSpareThreads;
	}

	public void setMinSpareThreads(int minSpareThreads) {
		this.minSpareThreads = minSpareThreads;
		if (executor != null) {
			executor.setCorePoolSize(minSpareThreads);
		}
	}

	@Override
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public boolean isPrestartminSpareThreads() {

		return prestartminSpareThreads;
	}

	public void setPrestartminSpareThreads(boolean prestartminSpareThreads) {
		this.prestartminSpareThreads = prestartminSpareThreads;
	}

	public int getMaxQueueSize() {
		return maxQueueSize;
	}

	public void setMaxQueueSize(int size) {
		this.maxQueueSize = size;
	}

	public long getThreadRenewalDelay() {
		return threadRenewalDelay;
	}

	public void setThreadRenewalDelay(long threadRenewalDelay) {
		this.threadRenewalDelay = threadRenewalDelay;
		if (executor != null) {
			executor.setThreadRenewalDelay(threadRenewalDelay);
		}
	}

	// Statistics from the thread pool
	@Override
	public int getActiveCount() {
		return (executor != null) ? executor.getActiveCount() : 0;
	}

	public long getCompletedTaskCount() {
		return (executor != null) ? executor.getCompletedTaskCount() : 0;
	}

	public int getCorePoolSize() {
		return (executor != null) ? executor.getCorePoolSize() : 0;
	}

	public int getLargestPoolSize() {
		return (executor != null) ? executor.getLargestPoolSize() : 0;
	}

	@Override
	public int getPoolSize() {
		return (executor != null) ? executor.getPoolSize() : 0;
	}

	public int getQueueSize() {
		return (executor != null) ? executor.getQueue().size() : -1;
	}

	@Override
	public boolean resizePool(int corePoolSize, int maximumPoolSize) {
		if (executor == null)
			return false;

		executor.setCorePoolSize(corePoolSize);
		executor.setMaximumPoolSize(maximumPoolSize);
		return true;
	}

	@Override
	public boolean resizeQueue(int capacity) {
		return false;
	}

	@Override
	protected String getDomainInternal() {
		// No way to navigate to Engine. Needs to have domain set.
		return null;
	}

	@Override
	protected String getObjectNameKeyProperties() {
		StringBuilder name = new StringBuilder("type=Executor,name=");
		name.append(getName());
		return name.toString();
	}
}
